package fr.inserm.umr915.vcfserver.bio.berkeley;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import java.util.Collections;
import java.util.Set;
import java.util.regex.Pattern;


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.InputStreamSource;

import com.sleepycat.bind.tuple.IntegerBinding;
import com.sleepycat.bind.tuple.ShortBinding;
import com.sleepycat.bind.tuple.StringBinding;
import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;


import fr.inserm.umr915.vcfserver.bio.Segment;
import fr.inserm.umr915.vcfserver.util.ColumnSerializer;
import fr.inserm.umr915.vcfserver.util.NameExtractor;
import fr.inserm.umr915.vcfserver.xml.XmlStreamSerializer;
import fr.inserm.umr915.vcfserver.xml.XsdSerializer;


public abstract class DatabaseManager<T>
	{
	private static final Log LOG= LogFactory.getLog("fr.inserm.umr915");
	protected Pattern delim=Pattern.compile("[\t]");
	private DatabaseId.Binding dbIdBinding=new  DatabaseId.Binding();
	private String name;
	private XsdSerializer xsdSerializer=null;
	private short id;

	private InputStreamSource inputStreamSource=null;
	public DatabaseManager()
		{
		
		}
	
	protected abstract T parse(String line) throws IOException;
	public abstract TupleBinding<T> getDataBinding();
	public abstract XmlStreamSerializer<T> getXmlStreamSerializer();
	
	
	public NameExtractor<T> getNameExtractor()
		{
		return null;
		}
	
	public ColumnSerializer<T> getColumnSerializer()
		{
		return null;
		}
	
	public XsdSerializer getXsdSerializer()
		{
		return xsdSerializer;
		}
	public void setXsdSerializer(XsdSerializer xsdSerializer)
		{
		this.xsdSerializer = xsdSerializer;
		}
	
	
	public void setId(short id)
		{
		this.id = id;
		}
	public short getId()
		{
		return id;
		}
	
	public String getListName()
		{
		return getName()+"List";
		}
	
	public String getName()
		{
		if(this.name==null) return getClass().getSimpleName();
		return name;
		}
	
	public void setInputStreamSource(InputStreamSource inputStreamSource)
		{
		this.inputStreamSource = inputStreamSource;
		}
	
	public void setName(String name)
		{
		this.name = name;
		}
	
	
	protected boolean accept(String line)
		{
		return true;
		}
	
	protected boolean acceptChromosome(String chr)
		{
		return chr.indexOf('_')==-1;
		}
	
	protected Segment getSegment(T o)
		{
		return null;
		}

	public void setDelim(Pattern delim)
		{
		this.delim = delim;
		}
	
	public Pattern getDelim()
		{
		return delim;
		}
	
	@SuppressWarnings("unchecked")
	protected Set<String> getNames(T o)
		{
		return Collections.EMPTY_SET;
		}
	

	protected BufferedReader open() throws IOException
		{
		LOG.info("opening stream");
		InputStream in=this.inputStreamSource.getInputStream();
		LOG.info("stream opened");
		return new BufferedReader(new InputStreamReader(in));
		}
	
	protected void cleanup(Storage storage)
		{
		LOG.info("start cleanup "+getName());
		for(int side=0;side<3;++side)
			{
			boolean foundOne=false;
			long nRow=0L;
			LOG.info("start cleanup "+getName()+" side "+side);
			Cursor c=null;
			
			try {
				switch(side)
					{
					case 0:c=storage.id2object.openCursor(null, null);break;
					case 1:c=storage.name2id.openCursor(null, null);break;
					case 2:c=storage.loc2id.openCursor(null, null);break;
					default: throw new IllegalStateException();
					}
				
				DatabaseEntry key=new DatabaseEntry();
				DatabaseEntry data=new DatabaseEntry();
				
				while(c.getNext(key, data, LockMode.DEFAULT)==OperationStatus.SUCCESS)
					{
					
					DatabaseId dbid=null;
					switch(side)
						{
						case 0:dbid=dbIdBinding.entryToObject(key);break;
						case 1:dbid=dbIdBinding.entryToObject(data);break;
						case 2:dbid=dbIdBinding.entryToObject(data);break;
						default: throw new IllegalStateException();
						}
					
					
					if(dbid.getDatabase()!=this.getId()) continue;
					foundOne=true;
					++nRow;
					if(nRow%1000000L==0)
						{
						LOG.info("Cleaning row "+nRow);
						}
					
					if(c.delete()!=OperationStatus.SUCCESS)
						{
						LOG.error("Cannot remove "+dbid);
						}
					}
				} 
			catch(Exception e)
				{
				throw new RuntimeException(e);
				}
			finally
				{
				if(c!=null) c.close();
				}
			if(side==0 && !foundOne)
				{
				LOG.info("no records to clean for "+getName());
				break;
				}
			}
		
		}
	
	@SuppressWarnings("unchecked")
	public void fill(Storage storage) throws IOException
		{
		cleanup(storage);
		
		
		/*StatsConfig cfg=new StatsConfig();
		cfg.setFast(true);
		cfg.setShowProgressStream(System.err);
		EnvironmentStats stats= storage.env.getStats(cfg);*/
	
		
		Segment.Binding segmentBinding=new Segment.Binding(storage.getChromosomes());
		DatabaseEntry key=new DatabaseEntry();
		DatabaseEntry data=new DatabaseEntry();
		TupleBinding<T> binding=getDataBinding();
		String line=null;
		int nLine=0;
		int countIgnored=0;
		int longest=1;
		/*
		Map<String,List<Segment>> intervals=null;
		if(storage.getFactory().containsBean("exome.cleaner"))
			{
			LOG.info("loading exome cleaner");
			Set<Short> exomeIds=(Set<Short>)storage.getFactory().getBean("exome.cleaner");
			if(exomeIds!=null && !exomeIds.contains(this.getId()))
				{
				final int extend=500;
				intervals=new HashMap<String, List<Segment>>();
				Cursor c=null;
				try
					{
					c=storage.loc2id.openCursor(null, null);
					while(c.getNext(key, data, LockMode.DEFAULT)==OperationStatus.SUCCESS)
						{
						DatabaseId dbId=dbIdBinding.entryToObject(data);
						if(!exomeIds.contains(dbId.getDatabase())) continue;
						Segment segment=segmentBinding.entryToObject(key);
						
						segment=new Segment(segment.getChromosome(),
								Math.max(segment.getChromStart()-extend,0),
								segment.getChromEnd()+extend
								);
						
						
						
						List<Segment> segments=intervals.get(segment.getChromosome());
						if(segments==null)
							{
							segments=new ArrayList<Segment>();
							intervals.put(segment.getChromosome(),segments);
							}
						segments.add(segment);
						}
					for(String chrom:intervals.keySet())
						{
						List<Segment> segments=intervals.get(chrom);
						boolean ok=true;
						while(ok)
							{
							ok=false;
							Collections.sort(segments);
							int i=0;
							while(i+1< segments.size())
								{
								Segment seg1=segments.get(i);
								Segment seg2=segments.get(i+1);
								if(seg1.overlap(seg2))
									{
									seg1=new Segment(
										seg1.getChromosome(),
										Math.min(seg1.getChromStart(),seg2.getChromStart()),
										Math.max(seg1.getChromEnd(),seg2.getChromEnd())
										);
									segments.set(i, seg1);
									segments.remove(i+1);
									ok=true;
									}
								else
									{
									i++;
									}
								}
							}
						}
					}
				catch (Exception e) {
					throw new IOException("Boum",e);
					}
				finally
					{
					if(c!=null) c.close();
					}
				}
			}*/
		
		key=new DatabaseEntry();
		data=new DatabaseEntry();
		BufferedReader in=open();
		while((line=in.readLine())!=null)
			{
			T object=parse(line);
			if(object==null) continue;
			
			if(++nLine%10000000==0)
				{
				LOG.info(getClass().getSimpleName()+" "+storage.id_generator+" "+getName());
				
				}
			Segment segment=getSegment(object);
			
			if(segment!=null && storage.getChromosomes().getIdByName(segment.getChromosome())==(byte)-1)
				{
				continue;
				}
			
			/*
			if(segment!=null && intervals!=null)
				{
				List<Segment> segments= intervals.get(segment.getChromosome());
				if(segments==null) continue;
				boolean ok=false;
				for(int i=0;i< segments.size();++i)
					{
					Segment other=segments.get(i);
					if(other.getChromEnd()< segment.getChromStart()) continue;
					if(other.getChromStart()> segment.getChromEnd()) break;
					ok=true;
					break;
					}
				if(!ok)
					{
					++countIgnored;
					if(countIgnored%1000000==0)
						{
						LOG.info(getClass().getSimpleName()+" Ignore "+segment+" ignored:"+countIgnored);
						}
					continue;
					}
				}*/
			
			
			storage.id_generator++;
			DatabaseId dbId=new DatabaseId(storage.id_generator,getId());
			
			dbIdBinding.objectToEntry(dbId, key);
			binding.objectToEntry(object, data);
			
			if(storage.id2object.put(null, key, data)!=OperationStatus.SUCCESS)
				{
				throw new IOException("Cannot insert "+object);
				}
			
			
			if(segment!=null)
				{
				segmentBinding.objectToEntry(segment, key);
				dbIdBinding.objectToEntry(dbId, data);

				if(storage.loc2id.put(null, key, data)!=OperationStatus.SUCCESS)
					{
					throw new IOException("Cannot insert "+line);
					}
				
				longest=Math.max(segment.length(), longest);
				}
			
			Set<String> names=getNames(object);
			names.remove("NULL");
			names.remove("");
			names.remove(".");
			names.remove("?");
			names.remove("N/A");
			
			for(String name:names)
				{
				StringBinding.stringToEntry(name.toUpperCase(), key);
				dbIdBinding.objectToEntry(dbId, data);
				
				if(storage.name2id.put(null, key, data)!=OperationStatus.SUCCESS)
					{
					throw new IOException("Cannot insert "+line);
					}
				}
			}
		in.close();
		
		if(storage.dbid2longest!=null) //TODO
			{
			ShortBinding.shortToEntry(this.getId(), key);
			IntegerBinding.intToEntry(longest, data);
			if(storage.dbid2longest.put(null, key, data)!=OperationStatus.SUCCESS)
				{
				throw new IOException("Cannot insert "+line);
				}
			}

		//LOG.info(stats);
		LOG.info("close"+getName());
		}
		
	}
